/**
 * Copyright (c) 2017-2024 Nop Platform. All rights reserved.
 * Author: canonical_entropy@163.com
 * Blog:   https://www.zhihu.com/people/canonical-entropy
 * Gitee:  https://gitee.com/canonical-entropy/nop-entropy
 * Github: https://github.com/entropy-cloud/nop-entropy
 */
package com.gitee.wsl.struct.table.base


import com.gitee.wsl.ext.string.indexOfInRange
import kotlin.jvm.JvmOverloads
import kotlin.reflect.KClass

/**
 * 描述文本文件中的一段文本的所在位置。
 *
 * @author canonical_entropy@163.com
 */
class SourceLocation(
    val path: String,
    /**
     * 从1开始计数
     */
     val line: Int,
    /**
     * 从1开始计数
     */
     val col: Int,
     val len: Int,
     val pos: Int,
    /**
     * 用于在Excel文件中定位。如果sheet和cell非空，则line和col对应于Excel单元格文本中的位置
     */
     val sheet: String?,
     val cell: String?,
    /**
     * 调试用的附加信息
     */
     val ref: String?
){

    private var text: String? = null

    /**
     * path表示文件路径，sheet+cell表示文件内的单元路径。它们加在一起后形成复杂文件内的定位路径。line是在此路径基础上的按行定位
     */
    var cellPath: String? = null
        get() {
            if (sheet == null && cell == null) return path

            if (field != null) return field

            val sb = StringBuilder()
            sb.append(path).append('?')
            if (sheet != null) {
                sb.append(sheet)
            }
            if (cell != null) {
                sb.append('!').append(cell)
            }
            field = sb.toString()
            return field
        }
        private set

    override fun toString(): String {
        if (text == null) text = buildText()
        return text!!
    }

    fun buildText(): String {
        val sb = StringBuilder()
        sb.append('[').append(line).append(':').append(col).append(':').append(len).append(':')
            .append(
                pos
            )
        sb.append(']')
        sb.append(path)
        if (sheet != null) {
            sb.append('?')
            sb.append(sheet)
        }

        if (cell != null) {
            sb.append('!').append(cell)
        }

        if (ref != null) sb.append('#').append(ref)
        return sb.toString()
    }

    override fun hashCode(): Int {
        return toString().hashCode()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true

        if (other is SourceLocation) {
            return toString() == other.toString()
        }
        return false
    }

    fun isSameLine(loc: SourceLocation): Boolean {
        if (line == loc.line) return path == loc.path && sheet == loc.sheet && cell == loc.cell
        return false
    }

    fun addRef(ref: String): SourceLocation {
        if (ref.isEmpty()) return this

        var mergedRef = ref
        if (this.ref != null) mergedRef = this.ref + "|" + ref
        return SourceLocation(path, line, col, len, pos, sheet, cell, mergedRef)
    }

    fun position(line: Int, col: Int, len: Int): SourceLocation {
        return SourceLocation(path, line, col, len, 0, sheet, cell, ref)
    }

    fun offset(line: Int, col: Int): SourceLocation {
        return position(this.line + line, this.col + col, 0)
    }

    /**
     * 为了优化保留的临时缓存，对应于IResource.getExternalPath
     */

    var externalPath: String? = null


    companion object {
        private const val serialVersionUID = -5678978218084960219L

        var UNKNOWN: SourceLocation = fromPath("<unknown>")

        const val PROP_PATH: String = "path"
        const val PROP_LEN: String = "len"
        const val PROP_LINE: String = "line"
        const val PROP_COL: String = "col"
        const val PROP_SHEET: String = "sheet"
        const val PROP_CELL: String = "cell"
        const val PROP_REF: String = "ref"
        const val PROP_POS: String = "pos"

        /*fun getLocation(obj: Any): SourceLocation? {
            if (obj is ISourceLocationGetter) return (obj as ISourceLocationGetter).getLocation()
            return null
        }*/

        @JvmOverloads
        fun fromLine(path: String, line: Int, col: Int = 0, len: Int = 0): SourceLocation {
            var path = path
            val pos = path.indexOf('?')
            if (pos > 0) {
                var sheet = path.substring(pos + 1)
                var ref: String? = null
                var cell: String? = null
                path = path.substring(0, pos)
                var pos2 = sheet.indexOf('#')
                if (pos2 > 0) {
                    ref = sheet.substring(pos2 + 1)
                    sheet = sheet.substring(0, pos2)
                }
                pos2 = sheet.indexOf('!')
                if (pos2 > 0) {
                    cell = sheet.substring(pos2 + 1)
                    sheet = sheet.substring(0, pos2)
                }
                return SourceLocation(path, line, col, len, 0, sheet, cell, ref)
            }
            return SourceLocation(path, line, col, len, 0, null, null, null)
        }

        fun <T : Any> fromClass(clazz: KClass<T>): SourceLocation {
            return fromPath("class:" + clazz.simpleName)
        }

        fun fromPath(path: String): SourceLocation {
            return fromLine(path, 1, 0, 0)
        }

        /*fun fromMap(map: Map<String?, Any?>): SourceLocation {
            val path: String = ConvertHelper.toString(map[PROP_PATH])
            val line: Int = ConvertHelper.toPrimitiveInt(map[PROP_LINE]) { NopException() }
            val col: Int = ConvertHelper.toPrimitiveInt(map[PROP_COL]) { NopException() }
            val len: Int = ConvertHelper.toPrimitiveInt(map[PROP_LEN]) { NopException() }
            val pos: Int = ConvertHelper.toPrimitiveInt(map[PROP_POS]) { NopException() }
            val sheet: String = ConvertHelper.toString(map[PROP_SHEET])
            val cell: String = ConvertHelper.toString(map[PROP_CELL])
            val ref: String = ConvertHelper.toString(map[PROP_REF])
            return SourceLocation(path, line, col, len, pos, sheet, cell, ref)
        }*/

        /**
         * 解析sourceLocation文本表示。其格式为 [line:col:len:pos]path?sheet!cell#ref
         *
         * @param loc SourceLocation.toString()得到的文本
         */

        fun parse(loc: String): SourceLocation? {
            if (loc.isEmpty()) return null

            val posBracket = loc.indexOf(']')

            val pos2: Int = loc.indexOfInRange(char = ':', startIndex = 1, endIndex = posBracket)//ApiStringHelper.indexOfInRange(loc, ':', 1, posBracket)
            if (posBracket < 0 || pos2 < 0 || loc[0] != '[') throw newCheckError(loc, "line")

            val pos3: Int = loc.indexOfInRange( ':', pos2 + 1, posBracket)
            if (pos3 < 0) throw newCheckError(loc, "col")

            val posX: Int = loc.indexOfInRange( ':', pos3 + 1, posBracket)
            if (posX < 0) throw newCheckError(loc, "pos")

            val pos4 = loc.indexOf('#', posBracket)
            val tagStart = if (pos4 < 0) loc.length else pos4
            val pos5 = loc.lastIndexOf('?', tagStart - 1)
            val pathEnd = if (pos5 > 0) pos5 else tagStart

            val line: Int = loc.substring(1, pos2).toInt()
            val col: Int = loc.substring(pos2 + 1, pos3).toInt()
            val len: Int = loc.substring(pos3 + 1, posX).toInt()
            val pos: Int = loc.substring(posX + 1, posBracket).toInt()

            /*val line: Int = ConvertHelper.toInt(loc.substring(1, pos2)) { err -> newCheckError(loc, "line") }
            val col: Int = ConvertHelper.toInt(loc.substring(pos2 + 1, pos3)) { err ->
                newCheckError(
                    loc,
                    "col"
                )
            }
            val len: Int = ConvertHelper.toInt(loc.substring(pos3 + 1, posX)) { err ->
                newCheckError(
                    loc,
                    "len"
                )
            }
            val pos: Int = ConvertHelper.toInt(loc.substring(posX + 1, posBracket)) { err ->
                newCheckError(
                    loc,
                    "pos"
                )
            }*/

            val path = loc.substring(posBracket + 1, pathEnd)
            val tag = if (pos4 > 0) loc.substring(pos4 + 1) else null
            var sheet: String? = null
            var cell: String? = null

            if (line < 0) throw newCheckError(loc, "line")

            if (pos5 > 0) {
                val pos6: Int = loc.indexOfInRange( '!', pos5 + 1, tagStart)
                if (pos6 > 0) {
                    sheet = loc.substring(pos5 + 1, pos6)
                    cell = loc.substring(pos6 + 1, tagStart)
                } else {
                    sheet = loc.substring(pos5 + 1, tagStart)
                }
            }

            return SourceLocation(path, line, col, len, pos, sheet, cell, tag)
        }

        private fun newCheckError(loc: String, part: String): Exception {
            return IllegalArgumentException("ERR_INVALID_SOURCE_LOCATION_STRING loc:$loc,part:$part")
                /*.param(ARG_VALUE, loc)
                .param(ARG_PART, part)*/
        }
    }
}